from abc import abstractmethod
import time
import os
import sys
import random
namespace = {}


def get_real(val):
    try:
        return val.read()
    except:
        return val


class Gener:
    @abstractmethod
    def read(self):
        pass

    def __str__(self):
        return str(self.read())

    def write(self, file):
        return file.write(str(self))

    @abstractmethod
    def clear(self):
        pass

class Str(Gener):
    def __init__(self,text):
        self.text=text
    def read(self):
        return self.text
    def clear(self):
        pass

class Var(Gener):
    def __init__(self, name, val=None):
        self.name = name
        self.val = val

    def read(self):
        global namespace
        if self.name not in namespace:
            self.inited = True
            namespace[self.name] = get_real(self.val)
        return namespace[self.name]

    def clear(self):
        self.inited = False


class Expr(Gener):
    def __init__(self, expr):
        self.expr = expr

    def read(self):
        return eval(self.expr, {},namespace)

    def clear(self):
        pass


class RandGen(Gener):
    def __init__(self, *val_range, is_int=True, is_genfunc=False):
        self.args = {
            'val_range': val_range,
            'is_int': is_int
        }
        self.val_range = None
        self.is_int = None
        self.inited = False
        self.is_genfunc = is_genfunc

    def get_num(self):
        v = None
        if self.is_int:
            v = random.randint(self.val_range[0], self.val_range[1])
        else:
            v = random.random() * \
                (self.val_range[1]-self.val_range[0])+self.val_range[0]
        return v

    def read(self):
        if not self.inited:
            self.val_range = tuple([get_real(x)
                                   for x in self.args['val_range']])
            self.is_int = get_real(self.args['is_int'])
            if self.is_int:
                self.val_range = tuple([int(x) for x in self.val_range])
        if self.is_genfunc:
            return self
        return self.get_num()

    def clear(self):
        self.inited = False

    def __call__(self):
        if self.is_genfunc:
            return self.get_num()


class RandChoose(Gener):
    def __init__(self, *args, vals=None):
        self.vals = vals or args

    def read(self):
        return get_real(random.choice(self.vals))


class GenGroup(Gener):
    def __init__(self, geners=None, cnt=1):
        self.geners = geners or []
        self.cnt = cnt

    def add_gener(self, gener):
        self.geners.append(gener)

    def write(self, file):
        for gener in self.geners:
            gener.write(file)

    def clear(self):
        for gener in self.geners:
            gener.clear()


class ENDL:
    @classmethod
    def write(cls, file):
        file.write('\n')

    @classmethod
    def clear(cls):
        pass

class Skipper(Gener):
    def __init__(self,*geners):
        self.geners=geners
    def write(self,file):
        for gener in self.geners:
            gener.read()
    def clear(self):
        pass

def empty_func_true(*args,**kwargs):
    return True

class FinalGener:
    def __init__(self, name, solution, basepath=None, subgeners=None):
        self.geners = subgeners or []
        self.name = name
        self.solution = solution
        self.basepath = os.path.join(
            sys.path[0], basepath or os.path.join('.', self.name))
        if not os.path.exists(self.basepath):
            os.mkdir(self.basepath)

    def write(self,check=None):
        '''
        check(id,output_path,runtime)->bool determines whether the data is ok
        '''
        if not check:
            check=empty_func_true
        id_ = 1
        last_time=0
        for gener in self.geners:
            for _ in range(gener.cnt):
                input_path = os.path.join(
                    self.basepath, self.name+str(id_)+'.in')
                output_path = os.path.join(
                    self.basepath, self.name+str(id_)+'.out')
                while not os.path.exists(output_path) or not check(id_,output_path,last_time):
                    gener.clear()
                    global namespace
                    namespace.clear()
                    if os.path.exists(input_path):
                        os.remove(input_path)
                    if os.path.exists(output_path):
                        os.remove(output_path)
                    with open(input_path, 'a') as f:
                        gener.write(f)
                    t=time.time()
                    os.system(
                        f'{os.path.join(self.basepath,self.solution)}<{input_path}>{output_path}')
                    last_time=time.time()-t
                id_ += 1
                print(f'Success generated data {input_path}')

    def checkone(self, id_, tester):
        self.write()
        input_path = os.path.join(self.basepath, self.name+str(id_)+'.in')
        output_path = os.path.join(
            self.basepath, self.name+str(id_)+'-test'+'.out')
        ans_path = os.path.join(self.basepath, self.name+str(id_)+'.out')

        os.system(
            f'{os.path.join(self.basepath,tester)}<{input_path}>{output_path}')
        with open(output_path) as f:
            output = f.readlines()
        with open(ans_path) as f:
            ans = f.readlines()
        for lid, (a, b) in enumerate(zip(output, ans)):
            if a.strip(' ') != b.strip(' '):
                print(lid)
                return False
        return True
